#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G15 -- S12 ------------------------------------------------------------##
__all__ = []

from .. import autostacks
from .. import comparecards as cmp_cards
from .. import formlib as layout
from .. import hint
from .. import pilelib as stack
from .. import quickplayscore as quickplay
from ..gamelib import info as gi, game
from ..hint import CautiousDefaultHint
from ..packlib import info as cs, place
from ..pilelib import move as a_move
from ..utillib.misc import kwdefault
from . import picturegallery

GT, SL = gi.GT, gi.SL
StackWrapper = stack.StackWrapper


class Hint(hint.DefaultHint):

    def compute_hints(self):
        """
        compute_hints() -> None

        compute hints - main hint intelligence
        """
        rows = self._stacks['set']['rows']
        RSTEP, RBASE = rows[0].game.RSTEP, rows[0].game.RBASE  # FIXME kludge
        for from_stack in (s for s in rows if s.cards):
            assert len(from_stack.cards) == 1 and from_stack.TOPCARD.face_up
            from_rank, pile = from_stack.BASECARD['rank'], from_stack.cards
            if from_stack.id % RSTEP > 0:
                left_rank = rows[from_stack.id - 1].TOPCARD['rank']
            else:
                left_rank = None
                if from_rank == RBASE:  # Keep W edge if rank correct
                    continue
            for to_stack in (s for s in rows if not s.cards):
                if self.shall_move_pile(from_stack, to_stack, pile, []):
                    score = 50000
                    if left_rank is not None:
                        score = 40000 + (self.K - left_rank)
                    self.add_hint(score, 1, from_stack, to_stack)


class CautiousHint(hint.CautiousHintMethods, Hint):
    pass


class SiebenBisAs_Hint(hint.CautiousDefaultHint):

    def compute_hints(self):
        """
        compute_hints() -> None

        compute hints - main hint intelligence
        """
        foundations = self._stacks['set']['foundations']
        rows = self._stacks['set']['rows']
        drop_group = self._stacks['grp']['dropstacks']
        for from_stack in (s for s in drop_group if s.cards):
            assert len(from_stack.cards) == 1 and from_stack.TOPCARD.face_up
            pile = from_stack.cards
            to_stack, ncards = from_stack.cardsDrop(foundations)
            if to_stack:
                self._score = 0
                self.score_drop_card(from_stack, to_stack, ncards)
                self.add_hint(ncards, from_stack, to_stack)
            for to_stack in (s for s in rows if not s.cards):
                if self.shall_move_pile(from_stack, to_stack, pile, []):
                    self._score = 50000
                    self.add_hint(ncards=1, from_stack=from_stack,
                                  to_stack=to_stack)


class Montana_Talon(stack.DealRowRedealTalon):
    """
    Reset pile not seq from W edge _row_. Redeal _row_ with gap after seq.
    """

    @property
    def _piles(self):
        """
        _piles -> tup

        :ivar tuple _gaps: 1st pile after a row sequence
        :return: deal out-of-sequence piles on Game.s.rows
        :rtype: tuple
        """
        RSTEP = self.game.RSTEP
        piles, gaps = [], []
        rows = self.game.s['rows']
        for i in range(0, len(rows), RSTEP):
            gap = False
            for row in (r for r in rows[i:i + RSTEP] if not r.is_filled):
                piles += rows
                if not gap:
                    gaps += rows
                    gap = True
        self._gaps = tuple(gaps)
        return tuple(piles)

    def deal_cards(self, rows=None, shuffle=True, sound=False, reverse=False,
                  **kw):
        """
        deal_cards([sound:bool=False, shuffle=False, reverse=False]) -> int

        Actual dealing, called from game.talon_deal(). Overide default, to
        allow cards from _piles to be reset.

        :keyword boolean sound: sound fx?
        :keyword boolean shuffle: shuffle cards before redealing?
        :keyword boolean reverse: deals cards in reverse order?
        :return: number of cards dealt.
        """
        ncards = 0
        game = self.game
        if sound and game.can_play_sound:
            game._play_sound('deal')
        if not self.cards:
            ncards += self._redeal(rows=self._piles, frames=4)
            if shuffle:
                game._move_do(a_move.AShuffleStackMove(self, game.random)
                              )  # low level
            if reverse:
                self.cards.reverse()
            game._move_do(a_move.ANextRoundMove(self), reset=True)  # low level
        for row in (pile for pile in self._piles if pile not in self._gaps):
            self.move_pile(1, row, frames=4)
        assert len(self.cards) == 0
        return ncards


class Spaces_Talon(Montana_Talon):
    """
    Reset pile not seq from W edge _row_. Redeal _row_ with random gap after
    seq.
    """

    @property
    def _piles(self):
        """
        _piles -> tup (tup)

        :ivar tuple _gaps: random pile of out-of-sequence piles.
        :return: deal out-of-sequence piles on Game.s.rows.
        :rtype: tuple
        """
        piles, gaps = super(Spaces_Talon, self)._piles()
        gaps = set()
        while len(gaps) != len(self.game.s['rows']) / self.game.RSTEP:
            gaps.add(self.game.random.choice(piles))
        self._gaps = tuple(gaps)
        return tuple(piles)


class Spoilt_Waste(picturegallery.Swap_StackMethods, stack.WasteStack):
    """
    Build none. Top card swaps _row_ pile.
    """
    pass


class SiebenBisAs_Foundation(stack.SS_Foundation):
    """
    Build up in rank by same suit wrap from any _row_ but 1st of each row with
    W pile gap.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent, then whether from _row_ with left neighbor gap.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean
        """
        if not super(SiebenBisAs_Foundation, self).cardsBuild(from_stack,
                                                              cards):
            return False
        if not from_stack in self.game.s['rows'] or from_stack.id % 10 == 0:
            return False
        return not self.game.s['rows'][from_stack.id - 1].cards


class Left_SS_RowStack(stack.BasicRowStack):
    """
    Play top on gap build up in rank by same suit from W neighbor.
    _BOTTOM_IMAGE_ is shade.
    """
    _BOTTOM_IMAGE_ = 'shade'

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        return (len(cards) == 2 and
                cmp_cards.isSameSuitSequence(cards, **self.cap))

    def _isBuildSequence(self, cards):
        """
        _isBuildSequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        cards = self.game.s['rows'][self.id - 1].cards[-1:] + cards
        return self._sequence(cards)

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent, then whether W edge is Game.RBASE or builds from
        left neighbor.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean
        """
        if not super(Left_SS_RowStack, self).cardsBuild(from_stack, cards):
            return False
        if self.id % self.game.RSTEP == 0:
            return cards[0]['rank'] == self.game.RBASE
        return self._isBuildSequence(cards, **self.cap)

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard.
        """
        self.is_filled = self.cardsBuild(None, self.cards)

    def clickHandler(self, event):
        """
        clickHandler(event:obj) -> bool

        A <left-click> will ⟪quickplay⟫ on gap else call parent method.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        if not self.cards:
            return self.quickPlayHandler(event)
        return super(Left_SS_RowStack, self).clickHandler(event)

    prepareBottom = stack.BasicRowStack.prepareInvisibleBottom


class Either_SS_RowStack(Left_SS_RowStack):
    """
    Play top on gap build up in rank by same suit from W or E neighbor.
    _BOTTOM_IMAGE_ is shade.
    """

    def _isBuildSequence(self, cards):
        """
        _isBuildSequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        if super(Either_SS_RowStack, self)._isBuildSequence(cards):
            return True
        if not self.id < len(self.game.s['rows']) - 1:
            cards += self.game.s['rows'][self.id + 1].cards[-1:]
            return self._sequence(cards)
        return False


class Left_RK_RowStack(Left_SS_RowStack):
    """
    Play top on gap build up in rank alone from W neighbor. _BOTTOM_IMAGE_ is
    shade.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        return (len(cards) == 2 and
                cmp_cards.isRankSequence(cards, **self.cap))


class SpacesAndAces_RowStack(Left_SS_RowStack):
    """
    Play top on gap build up in any higher rank by same suit from W neighbor.
    _BOTTOM_IMAGE_ is shade.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        return (len(cards) == 2 and cmp_cards.isSameSuit(cards) and
                cards[0]['rank'] < cards[1]['rank'])


class Paganini_RowStack(Left_SS_RowStack):
    """
    Play top on gap build up in rank by same suit from W neighbor.
    _BOTTOM_IMAGE_ is shade.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        return (len(cards) == 2 and (cmp_cards.isSameSuit(cards) and
               cmp_cards.isSequenceWithRanks(cards, self.game['ranks'],
                                              **self.cap)))


class Spoilt_RowStack(Paganini_RowStack):
    """
    Build up from W of gap in rank by same suit wrap. Play top. _BOTTOM_IMAGE_
    is shade.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent, then whether card rank matches position and card suit
        matches row.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean
        """
        if not super(Spoilt_RowStack, self).cardsBuild(from_stack, cards):
            return False
        card = cards[0]
        card_rank = self.game['ranks'].index(card['rank'])
        RSTEP = self.game.RSTEP
        row, col = divmod(self.id, RSTEP)
        if card_rank != col:
            return False
        curr_row = range(row * RSTEP, row * RSTEP + RSTEP)
        for pile in self.game.s['rows']:
            base_card = pile.BASECARD
            if (base_card and base_card.face_up and
                cmp_cards.isSameSuit(base_card + card)):
                return row.id in curr_row
        return True


class SiebenBisAs_RowStack(stack.BasicRowStack):
    """
    Play top on gap of rank position in row of same suit from W neighbor.
    _BOTTOM_IMAGE_ is shade.
    """
    _BOTTOM_IMAGE_ = 'shade'

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent, then whether builds from left neighbor or if left most
        from right neighbor.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean
        """
        if not super(SiebenBisAs_RowStack, self).cardsBuild(from_stack,
                                                            cards):
            return False
        if self.id % 10 != 0:  # left neighbour
            next = self.game.s['rows'][self.id - 1].cards[-1:]
            if next and cmp_cards.isSameSuitSequence(next + cards,
                                                     **self.cap):
                return True
        if self.id % 10 != 9:  # right neighbour
            next = self.game.s['rows'][self.id + 1].cards[-1:]
            if next and cmp_cards.isSameSuitSequence(cards + next,
                                                     **self.cap):
                return True
        return False

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard.
        """
        self.is_filled = self.cardsBuild(None, self.cards)


class Maze_RowStack(stack.BasicRowStack):
    """
    Build up from W E of gap in rank by same suit wrap. Play top.
    _BOTTOM_IMAGE_ = 'shade'
    """
    _BOTTOM_IMAGE_ = 'shade'

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent, then whether builds from left neighbor or if left most
        from right neighbor.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean
        """
        if not super(Maze_RowStack, self).cardsBuild(from_stack, cards):
            return False
        # left neighbour
        next_ = self.game.s['rows'][(self.id - 1) % 54].TOPCARD
        if next_:
            if cmp_cards.isSameSuitSequence([next_] + cards, **self.cap):
                return True
            if next_['rank'] == cs.QUEEN and cards[0]['rank'] == cs.ACE:
                return True
        # right neighbour
        next_ = self.game.s['rows'][(self.id + 1) % 54].TOPCARD
        if next_:
            if cmp_cards.isSameSuitSequence(cards + [next_], **self.cap):
                return True
        return False

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard.
        """
        self.is_filled = self.cardsBuild(None, self.cards)

    prepareBottom = stack.Stack.prepareInvisibleBottom


class Montana(game.Game):
    """
    Arrange full seq per row of _row_ within 3 turn.

    _row_
      Pile 52 over 4 row. Init deal 1 card, drop ACE off form init gap. Play
      top on gap build up in rank by same suit from W neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with
      gap after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=3)
    :cvar Stack _row_: Left_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=53, name='Montana', game_type=(GT.MONTANA | GT.OPEN),
                    decks=1, redeals=2, version='3.00', altnames='Gaps',
                    skill_level=SL.MOSTLY_SKILL, si={'ncards': 48})
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = StackWrapper(Montana_Talon, max_rounds=3)
    _row_ = StackWrapper(Left_SS_RowStack, max_accept=1, max_cards=1, dir=1)
    _hint_ = Hint

    RSTEP, RBASE = 13, cs.TWO

    def _create_game(self, rows=52, round_text=True, **kw):
        """
        Create game format.

        Override pile placements based on current attributes alone.

        .. \_create_game(rows=52, round_text=True) -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + self.RSTEP * l.XS,
                           l.YM + l.NSUITS * l.YS + l.TEXTHEIGHT,)
        y = l.YM
        for i in range(rows / self.RSTEP):
            x = l.XM
            for j in range(self.RSTEP):
                s['rows'].append(self._row_(x, y, game=self))
                x += l.XS
            y += l.YS
        if round_text:
            x, y = l.XM + (self.RSTEP - 1) * l.XS / 2, self.height - l.YS
            s['talon'] = self._talon_(x, y, game=self)
            l.set_rounds_text(s['talon'], 'se')
        else:
            coords = self.canvas._get_hidden_coords(self.images.size)
            s['talon'] = self._talon_(coords.x, coords.y, game=self)
        if self.RBASE:  # Invisible stack for ACE
            s['internals'].append(stack.InvisibleStack(self))
        l.defaultStackGroups()
        return l

    def _start(self, drop=cs.ACE, **KW):
        frames = 0
        RLEN, RSTEP = len(self.s['rows']), self.RSTEP
        for row in self.s['rows']:
            if self.s['talon'].TOPCARD['rank'] == drop:
                self.s['talon'].dealRow(rows=self.s['internals'], frames=0)
            else:
                if row.id >= RLEN - RSTEP:  # last row
                    self._play_sound('deal')
                    frames = 4
                self.s['talon'].dealRowAvail(rows=(row,), frames=frames)

    @property
    def is_complete(self):
        rows = self.s['rows']
        ranks = len(self.game['ranks'])
        for j in range(0, len(rows), self.RSTEP):
            s = stack.getPileFromStacks(rows[j:j + ranks])
            if not s:
                return all(s._is_filled for p in s)

    @property
    def _highlight_piles(self):
        """
        Return piles to highlight for possible move.

        :return: empty.
        :rtype: tuple
        """
        return ()

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable autodrop.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        return autostacks.flip_quickplay(super(Montana, self)._autopiles(event))

    _quickplay_ = quickplay.Montana


class Spaces(Montana):
    """
    Arrange full seq per row of _row_ within 3 turn.

    _row_
      Pile 52 over 4 row. Init deal 1 card, drop ACE off form init gap. Play
      top on gap build up in rank by same suit from W neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with random
      gap after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Spaces_Talon (max_rounds=3)
    :cvar Stack _row_: Left_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=116, name='Spaces', game_type=(GT.MONTANA | GT.OPEN),
                    decks=1, redeals=2, version='3.10',
                    skill_level=SL.MOSTLY_SKILL, si={'ncards': 48})
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = StackWrapper(Spaces_Talon, max_rounds=3)


class BlueMoon(Montana):
    """
    Arrange full seq per row of _row_ within 3 turn.

    _row_
      Pile 56 over 4 row. Init deal 1 card, drop ACE W edge form init gap.
      Play top on gap build up in rank by same suit from W neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with
      gap after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=3)
    :cvar Stack _row_: Left_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=63, name='Blue Moon', game_type=(GT.MONTANA | GT.OPEN |
                    GT.XPAT), decks=1, redeals=2, version='3.00',
                    altnames=('Rangoon',), skill_level=SL.MOSTLY_SKILL)
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    RSTEP, RBASE = 14, cs.ACE

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current attributes alone.

        .. \_create_game(rows=56, round_text=True) -> Layout
        """
        kw.setdefault('row', 56)
        return super(BlueMoon, self)._create_game(**kw)

    def _start(self, drop=cs.ACE, **kw):
        j, frames = 0, 0
        RLEN = len(self.s['rows'])
        for row in self.s['rows']:
            if self.s['talon'].TOPCARD['rank'] == drop:  # Deal W edge pile
                self.s['talon'].dealRow(rows=(self.s['rows'][j],), frames=0)
                j += self.RSTEP
            elif row.id % self.RSTEP != 0:  # Deal other piles
                if row.id == RLEN - self.RSTEP:  # last row
                    self._play_sound('deal')
                    frames = -1
                self.s['talon'].dealRowAvail(rows=(row,), frames=frames)


class RedMoon(BlueMoon):
    """
    Arrange full seq per row of _row_ within 3 turn.

    _row_
      Pile 56 over 4 row. Init deal 1 card with ACE W edge and gap follow form
      init gap. Play top on gap build up in rank by same suit from W neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with
      gap after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=3)
    :cvar Stack _row_: Left_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=117, name='Red Moon', game_type=(GT.MONTANA | GT.OPEN),
                    decks=1, redeals=2, version='3.10',
                    skill_level=SL.MOSTLY_SKILL)
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return ACE on top by rank, suit, deck for _foundation_ deal.
        """
        return place.MoveToTop(cards, place.AllAce)

    def _start(self, **kw):
        frames = 0
        RLEN = len(self.s['rows'])
        self.s['talon'].dealRow(rows=(self.s['rows'][::self.RSTEP]), frames=frames)
        for i in range(2, RLEN, self.RSTEP):
            if i >= RLEN - self.RSTEP:  # last row:
                self._play_sound('deal')
                frames = 4
            self.s['talon'].dealRow(rows=self.s['rows'][i:i + 12], frames=frames)


class Galary(RedMoon):
    """
    Arrange full seq per row of _row_ within 3 turn.

    _row_
      Pile 56 over 4 row. Init deal 1 card with ACE W edge and gap follow form
      init gap. Play top on gap build up in rank by same suit from W E
      neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with
      gap after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=3)
    :cvar Stack _row_: Either_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: CautiousHint
    """
    __info__ = dict(id=275, name='Galary', game_type=(GT.MONTANA | GT.OPEN |
                    GT.ORIGINAL), decks=1, redeals=2, version='0.80fc',
                    skill_level=SL.MOSTLY_SKILL)
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = StackWrapper(Either_SS_RowStack, dir=1, max_accept=1, max_cards=1)
    _hint_ = CautiousHint


class Moonlight(Montana):
    """
    Arrange full seq per row of _row_ within 3 turn.

    _row_
      Pile 52 over 4 row. Init deal 1 card, drop ACE off form init gap. Play
      top on gap build up in rank by same suit from W E neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with
      gap after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=3)
    :cvar Stack _row_: Either_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: CautiousHint
    """
    __info__ = dict(id=276, name='Moonlight', game_type=(GT.MONTANA | GT.OPEN
                    | GT.ORIGINAL), decks=1, redeals=2, version='0.80fc',
                    skill_level=SL.MOSTLY_SKILL, si={'ncards': 48})
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = StackWrapper(Either_SS_RowStack, max_accept=1, max_cards=1, dir=1)
    _hint_ = CautiousHint


class Jungle(BlueMoon):
    """
    Arrange full seq per row of _row_ within 2 turn.

    _row_
      Pile 56 over 4 row. Init deal 1 card, drop ACE drop W edge form init
      gap. Play top on gap build up in rank alone from W neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with
      gap after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=2)
    :cvar Stack _row_: Left_RK_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: CautiousHint
    """
    __info__ = dict(id=380, name='Jungle', game_type=(GT.MONTANA | GT.OPEN),
                    decks=1, redeals=1, skill_level=SL.MOSTLY_SKILL,
                    version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = StackWrapper(Montana_Talon, max_rounds=2)
    _row_ = StackWrapper(Left_RK_RowStack, max_accept=1, max_cards=1, dir=1)
    _hint_ = CautiousHint


class SpacesAndAces(BlueMoon):
    """
    Arrange full seq per row of _row_ within a turn.

    _row_
      Pile 56 over 4 row. Init deal 1 card with W edge gap form init
      gap. Play top on gap build in greater rank by same suit from W
      neighbor. Full seq must be up in rank by same suit.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _row_: SpacesAndAces_RowStack (max_accept=1, max_cards=1,
      dir=1)
    :cvar Hint _hint_: CautiousHint
    """
    __info__ = dict(id=381, name='Spaces and Aces', game_type=(GT.MONTANA |
                    GT.OPEN), decks=1, skill_level=SL.MOSTLY_SKILL,
                    version='0.90fc')
    __author__ = 'Skomoroh'
    __credits__ = ['Robert Harbin']
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _row_ = StackWrapper(SpacesAndAces_RowStack, max_accept=1, max_cards=1,
                         dir=1)
    _hint_ = CautiousHint

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current attributes alone.

        .. \_create_game(rows=56, round_text=False) -> Layout
        """
        kw.setdefault('round_text', False)
        return super(SpacesAndAces, self)._create_game(**kw)

    def _start(self, **kw):
        kw.setdefault('drop', cs.GAP)
        super(SpacesAndAces, self)._start(**kw)


class Paganini(BlueMoon):
    """
    Arrange full seq per row of _row_ within 2 turn.

    _row_
      Pile 40 over 4 row. Init deal 1 card, drop ACE drop W edge form init
      gap. Play top on gap build up in rank by same suit from W neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with gap
      after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=2)
    :cvar Stack _row_: Paganini_RowStack (max_accept=1, max_cards=1, dir=1,
      mod=9)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=706, name='Paganini', game_type=(GT.MONTANA | GT.OPEN),
                    decks=1, redeals=1, skill_level=SL.MOSTLY_SKILL,
                    ranks=cs.SHORT_A6K[1:] + (cs.ACE,),
                    altnames=('Long Trip',), version='1.00fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    RSTEP = 10

    _talon_ = StackWrapper(Montana_Talon, max_rounds=2)
    _row_ = StackWrapper(Paganini_RowStack, dir=1, max_accept=1, max_cards=1,
                         mod=9)

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current attributes alone.

        .. \_create_game(rows=40, round_text=True) -> Layout
        """
        kw.setdefault('row', 40)
        return super(Paganini, self)._create_game(**kw)


class Spoilt(BlueMoon):
    """
    Arrange full seq per row of _row_ within a turn.

    _row_
      Pile 32 over 4 row. Init deal 1 card with W edge gap form init gap.
      Play top on gap of rank position from SEVEN in row of same suit from
      W neighbor.

    _talon_
      Pile 1 S. Deal _waste_ a card.

    _waste_
      Pile 1 E of _talon_. Build none. Top card swaps _row_ pile.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: stack.WasteTalon
    :cvar Stack _talon_: Spoilt_Waste (max_cards=1)
    :cvar Stack _row_: SpacesAndAces_RowStack (max_accept=1, max_cards=1,
      min_cards=1)
    :cvar Hint _hint_: CautiousHint
    """
    __info__ = dict(id=736, name='Spoilt', game_type=GT.MONTANA, decks=1,
                    skill_level=SL.MOSTLY_LUCK, version='1.10fc',
                    ranks=cs.SHORT_A7K[1:] + (cs.ACE,))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    RSTEP, RBASE = 8, cs.SEVEN

    _talon_ = stack.WasteTalon
    _waste_ = StackWrapper(Spoilt_Waste, max_cards=1)
    _row_ = StackWrapper(Paganini_RowStack, max_accept=1, max_cards=2,
                         min_cards=1)

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current attributes alone.

        .. \_create_game(rows=32, round_text=True) -> Layout
        """
        kwd.setdefault('rows', 32)
        l, s = super(Spoilt, self)._create_game(**kw), self.s
        x, y = self.s['talon'].x, self.s['talon'].y
        x += l.XS
        s['wastes'].append(self._waste_(x, y, game=self))
        s['internals'].append(stack.InvisibleStack(self))
        l.defaultStackGroups()

    def _start(self, **kw):
        kw.setdefault('drop', cs.GAP)
        super(Spoilt, self)._start(**kw)
        self.s['talon'].deal_cards()


class DoubleMontana(BlueMoon):
    """
    Arrange full seq per row of _row_ within a turn.

    _row_
      Pile 112 over 8 row. Init deal 1 card with W edge gap form init gap.
      Play top on gap build up in rank by same suit from W neighbor.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _row_: Left_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: CautiousHint
    """
    __info__ = dict(id=759, name='Double Montana', game_type=(GT.MONTANA |
                    GT.OPEN), decks=2, skill_level=SL.MOSTLY_SKILL,
                    version='2.00fc', altnames=('Monatana (2 Decks)',))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _hint_ = CautiousHint

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current attributes alone.

        .. \_create_game(rows=112, round_text=False) -> Layout
        """
        kwdefault(kw, rows=112, round_text=False)
        return super(DoubleMontana, self)._create_game(**kw)

    def _start(self, **kw):
        kw.setdefault('drop', cs.GAP)
        super(DoubleMontana, self)._start(**kw)


class DoubleBlueMoon(BlueMoon):
    """
    Arrange full seq per row of _row_ within a turn.

    _row_
      Pile 112 over 8 row. Init deal 1 card,t drop ACE W edge form gap. Play
      top on gap build up in rank by same suit from W neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with gap
      after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=3)
    :cvar Stack _row_: Left_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=770, name='Double Blue Moon', game_type=(GT.MONTANA |
                    GT.OPEN), decks=2, redeals=2, skill_level=SL.MOSTLY_SKILL,
                    version='2.00fc', altnames=('Blue Moon (2 Decks)',))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = StackWrapper(Montana_Talon, max_rounds=3)

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current attributes alone.

        .. \_create_game(rows=112, round_text=True) -> Layout
        """
        kw.setdefault('rows', 112)
        return super(DoubleBlueMoon, self)._create_game(**kw)


class DoubleRedMoon(RedMoon):
    """
    Arrange full seq per row of _row_ within a turn.

    _row_
      Pile 112 over 8 row. Init deal 1 card with ACE W edge and gap follow
      form init gap. Play top on gap build up in rank by same suit from W
      neighbor.

    _talon_
      Pile 1 S. Reset pile not seq from W edge _row_. Redeal _row_ with gap
      after seq.

    note::
      ⟪highlight_play⟫ and ⟪autodrop⟫ disabled.
    ------
    :cvar Stack _talon_: Montana_Talon (max_rounds=3)
    :cvar Stack _row_: Left_SS_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=771, name='Double Red Moon', game_type=(GT.MONTANA |
                    GT.OPEN), decks=2, redeals=2, skill_level=SL.MOSTLY_SKILL,
                    version='2.00fc', altnames=('Red Moon (2 Decks)',))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = StackWrapper(Montana_Talon, max_rounds=3)

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current attributes alone.

        .. \_create_game(rows=112, round_text=True) -> Layout
        """
        kw.setdefault('rows', 112)
        return super(DoubleRedMoon, self)._create_game(**kw)


class SiebenBisAs(game.Game):
    """
    Finish cards on _foundation_ within a turn.

    _rows_
      Pile 30 over 3 row. Init deal 1 card. Build up from W of gap in rank
      by same suit wrap. Play top.

    _reserve_
      Pile 2 N _row_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 4 S _row_. Init finish SEVEN from _row_. Build up in rank by same
      suit wrap from any _row_ but 1st of each row with W pile gap.
    ------
    :cvar Stack _talon_: stack.stack.InitialDealstack.Talon
    :cvar Stack _foundation_: SiebenBisAs_Foundation (base_rank=SEVEN, mod=13,
      max_move=0, max_cards=8)
    :cvar Stack _row_: SiebenBisAs_RowStack (max_accept=1, max_cards=1, dir=1,
      mod=13)
    :cvar Stack _row_: stack.ReserveStack (max_accept=0)
    :cvar Hint _hint_: SiebenBisAs_Hint
    """
    __info__ = dict(id=118, name='Sieben bis As', game_type=(GT.MONTANA |
                    GT.OPEN), decks=1, skill_level=SL.MOSTLY_SKILL,
                    version='3.10', ranks=cs.SHORT_A7K)
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation_ = StackWrapper(SiebenBisAs_Foundation, base_rank=cs.SEVEN,
                                mod=13, max_move=0, max_cards=8)
    _row_ = StackWrapper(SiebenBisAs_RowStack, max_accept=1, max_cards=1,
                         dir=1, mod=13)
    _reserve_ = StackWrapper(stack.ReserveStack, max_accept=0)
    _hint_ = SiebenBisAs_Hint

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current attributes alone.

        .. \_create_game() -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 10 * l.XS, l.YM + 5 * l.YS,)
        y = l.YM + l.YS
        for i in range(3):
            x = l.XM
            for j in range(10):
                s['rows'].append(self._row_(x, y, game=self))
                x += l.XS
            y += l.YS
        x, y = l.XM + 4 * l.XS, l.YM
        for i in range(2):
            s['reserves'].append(self._reserve_(x, y, game=self))
            x += l.XS
        x = l.XM + 3 * l.XS
        y += 4 * l.YS
        for i in l.SUITS:
            s['foundations'].append(self._foundation_(x, y, game=self, suit=i))
            x += l.XS
        s['talon'] = self._talon_(l.XM, self.height - l.YS, self)
        l.defaultStackGroups()
        return l

    def _start(self):
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].dealRow(rows=self.s['reserves'])
        for row in (p for p in self.s['rows'] if p.TOPCARD['rank'] == cs.SEVEN):
            self.move(1, row, self.s['foundations'][row.TOPCARD['suit']])


class Maze(game.Game):
    """
    Finish cards on _foundation_ within a turn.

    _rows_
      Pile 54 over 4 row. Init deal 1 card with gap last 2. Build up from W E
      of gap in rank by same suit wrap. Play top.

    _internals_
      Pile 1. Init drop any KING while deal _row_.
    ------
    :cvar Stack _talon_: stack.stack.InitialDealstack.Talon
    :cvar Stack _row_: Maze_RowStack (max_accept=1, max_cards=1, dir=1)
    :cvar Hint _hint_: SiebenBisAs_Hint
    """
    __info__ = dict(id=144, name='Maze', game_type=(GT.MONTANA | GT.OPEN |
                    GT.GNOME), decks=1, version='3.21',
                    skill_level=SL.MOSTLY_SKILL, si={'ncards': 48})
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _row_ = StackWrapper(Maze_RowStack, max_accept=1, max_cards=1, dir=1)
    _hint_ = SiebenBisAs_Hint

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current attributes alone.

        .. \_create_game() -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 9 * l.XS, l.YM + 6 * l.YS,)
        y = l.YM
        for i in range(6):
            x = l.XM
            for j in range(9):
                s['rows'].append(self._row_(x, y, game=self))
                x += l.XS
            y += l.YS
        x, y = self.width - l.XS, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        s['internals'].append(stack.InvisibleStack(self))  # stack hold four KING
        l.defaultStackGroups()
        return l

    def _start(self):
        frames = 0
        for i in range(52):  # Last 2 empty
            if self.s['talon'].TOPCARD['rank'] == cs.KING:
                self.s['talon'].dealRow(rows=self.s['internals'], frames=0)
            else:
                if frames == 0 and i >= 36:
                    self._play_sound('deal')
                    frames = -1
                self.s['talon'].dealRow(rows=(self.s['rows'][i],), frames=frames)

    @property
    def is_complete(self):
        rows = [s for s in self.s['rows'] if s.cards]
        if len(rows) != 48:  # Cards dealt yet?
            return False
        # allow wrap around: search first Ace
        i = 0
        while rows[i].cards[-1]['rank'] != cs.ACE:
            i = i + 1
        rows += rows
        # now check for 4 sequences
        for j in range(i, i + 37, 12):
            cs = rows[j:j + 12]
            if (cs[-1].id - cs[0].id) % 54 != 11:  # found a space in sequence
                return False
            if cs[0].TOPCARD['rank'] != cs.ACE or cs[-1].TOPCARD['rank'] != cs.QUEEN:
                return False
            pile = stack.getPileFromStacks(cs)
            if not pile or not cmp_cards.isSameSuitSequence(pile, dir=1,
                                                            mod=13):
                return False
        return True


PROVIDES = [SiebenBisAs, Maze, Montana, Spaces, BlueMoon, RedMoon, Galary,
           Moonlight, Jungle, SpacesAndAces, Paganini, Spoilt, DoubleMontana,
           DoubleBlueMoon, DoubleRedMoon]